/**
 *   @author Claudius Tiberiu Iacob <claudius.iacob@gmail.com>
 */
package ro.ciacob.utils {
	import flash.desktop.Icon;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.GradientType;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.AsyncErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.filesystem.File;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;

	import org.bytearray.display.ScaleBitmap;

	import spark.primitives.Rect;

	public class Draw {

		public static function arrayToBitmapData(array:Array):BitmapData {
			if (array.length >= 4) {
				var height:int = array[array.length - 1] as int;
				var width:int = array[array.length - 2] as int;
				var isTransparent:Boolean = ((array[array.length - 3] as int) ==
					1);
				// Verify if stored with and height are acurate
				var surfaceOf:Number = (width * height);
				if (surfaceOf == array.length - 3) {
					var vector:Vector.<uint> = new Vector.<uint>;
					for (var i:int = 0; i < array.length - 3; i++) {
						vector[i] = (array[i] as uint);
					}
					var bitmapData:BitmapData = new BitmapData(width, height, isTransparent,
						0x00ffffff);
					var fullSpan:Rectangle = new Rectangle(0, 0, width, height);
					bitmapData.setVector(fullSpan, vector);
					return bitmapData;
				}
			}
			//trace('`Draw.arrayToBitmapData()`: Cannot read given array into a BitmapData; was the array created by `Draw.bitmapDataToArray()`?');
			return null;
		}

		/**
		 * Creates an untyped array out of a BitmapData.
		 * NOTES:
		 * - each entry in the array is an unsigned integer;
		 * - last three entries in the array have special meanings:
		 *   - the last (n) is the number of rows in the BitmapData (the pixel height);
		 *   - the last but one (n - 1) is the number of columns (the pixel width);
		 *   - the one before it (n - 2) is a flag, that indicates whether the
		 * 	   source BitmapData was transparent (1) or not (0).
		 */
		public static function bitmapDataToArray(bitmapData:BitmapData):Array {
			var fullSpan:Rectangle = new Rectangle(0, 0, bitmapData.width, bitmapData.
				height);
			var vector:Vector.<uint> = bitmapData.getVector(fullSpan);
			var array:Array = [];
			for (var i:int = 0; i < vector.length; i++) {
				array[i] = vector[i];
			}
			array.push(bitmapData.transparent ? 1 : 0);
			array.push(bitmapData.width);
			array.push(bitmapData.height);
			// A bit of cleanup
			fullSpan = null;
			vector.length = 0;
			vector = null;
			return array;
		}

		public static function cropBitmapData(bitmapData:BitmapData, area:Rectangle,
			horizontalAlignment:Number, verticalAlignment:Number):BitmapData {
			var transparent:Boolean = bitmapData.transparent;
			var result:BitmapData = new BitmapData(area.width, area.height, transparent,
				0x00000000);
			var matrix:Matrix = new Matrix();
			var dx:Number = (area.width - bitmapData.width) * horizontalAlignment;
			var dy:Number = (area.height - bitmapData.height) * verticalAlignment;
			matrix.translate(dx, dy);
			result.draw(bitmapData, matrix);
			return result;
		}

		/**
		 * Returns an array with BitmapData objects, that represent the icons the operating system
		 * uses for a specific file. Optionally, bitmaps in the array are sorted by height (otherwise,
		 * their order cannot be guaranteed).
		 *
		 * @param	file
		 * 			The file to retrieve icons for.
		 *
		 * @param	sortByHeight
		 * 			Whether to sort resulting bitmaps by their height (shortest one on first index).
		 *
		 * @return	An array with BitmapData objects, or null on error.
		 */
		public static function getFileIcons(file:File, sortByHeight:Boolean = true):Array {
			var icons:Array;
			if (file != null) {
				if (file.icon != null) {
					icons = Icon(file.icon).bitmaps;
					if (icons.length > 0 && sortByHeight) {
						icons.sort(function(a:Object, b:Object):int {
							var bmpA:BitmapData = (a as BitmapData);
							var bmpB:BitmapData = (b as BitmapData);
							if (bmpA != null && bmpB != null) {
								return (bmpA.height - bmpB.height);
							}
							return 0;
						});
					}
				}
			}
			return icons;
		}

		/**
		 * Creates a bitmap data out of a loadable image file. This is an asynchronous process.
		 */
		public static function imageToBitmapData(image:File, successCallback:Function,
			failureCallback:Function = null, callbackContext:Object = null, loader:Loader =
			null):void {
			if (callbackContext == null) {
				callbackContext = {};
			}
			if (loader == null) {
				loader = new Loader;
			}
			var removeListeners:Function = function():void {
				loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, internalSuccessCallback);
				loader.contentLoaderInfo.removeEventListener(AsyncErrorEvent.ASYNC_ERROR,
					internalFailureCallback);
				loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR,
					internalFailureCallback);
				loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.
					SECURITY_ERROR, internalFailureCallback);
			}
			var internalSuccessCallback:Function = function(event:Event):void {
				removeListeners();
				if (loader.content != null && (loader.content is Bitmap)) {
					var bitmapData:BitmapData = Bitmap(loader.content).bitmapData;
					successCallback.apply(callbackContext, [bitmapData]);
				}
			}
			var internalFailureCallback:Function = function(event:Event):void {
				removeListeners();
				if (failureCallback != null) {
					failureCallback.apply(callbackContext, [event]);
				}
			}
			if (!loader.hasEventListener(Event.COMPLETE)) {
				loader.contentLoaderInfo.addEventListener(Event.COMPLETE, internalSuccessCallback);
			}
			if (!loader.hasEventListener(AsyncErrorEvent.ASYNC_ERROR)) {
				loader.contentLoaderInfo.addEventListener(AsyncErrorEvent.ASYNC_ERROR,
					internalFailureCallback);
			}
			if (!loader.hasEventListener(IOErrorEvent.IO_ERROR)) {
				loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,
					internalFailureCallback);
			}
			if (!loader.hasEventListener(SecurityErrorEvent.SECURITY_ERROR)) {
				loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR,
					internalFailureCallback);
			}
			var request:URLRequest = new URLRequest(image.url);
			loader.load(request);
		}

		/**
		 * Draws a radial gradient on a given Sprite.
		 */
		public static function radialGradient(canvas:Sprite, radius:Number, colors:Array,
			offset:Point = null, alphas:Array = null, ratios:Array = null):void {
			if (offset == null) {
				offset = new Point(0, 0);
			}
			if (alphas == null) {
				alphas = [1, 1];
			}
			if (ratios == null) {
				ratios = [0, 255];
			}
			var matrix:Matrix = new Matrix;
			matrix.createGradientBox(2 * radius, 2 * radius, 0, -radius + offset.
				x, -radius + offset.y);
			canvas.graphics.beginGradientFill(GradientType.RADIAL, colors, alphas,
				ratios, matrix);
			canvas.graphics.drawCircle(0, 0, radius);
			canvas.graphics.endFill();
		}

		/**
		 * Creates a bitmap data out of an embedded image resource. This is a synchronous process.
		 */
		public static function resourceToBitmapData(image:Class):BitmapData {
			if (image != null) {
				var instance:Object;
				try {
					instance = new image;
				} catch (e:Error) {
					// Ignore
				}
				if (instance != null) {
					if (instance is Bitmap) {
						return Bitmap(instance).bitmapData;
					}
				}
			}
			return null;
		}

		/**
		 * Creates a scaled version of a given BitmapData.
		 */
		public static function scaleBitmapData(bitmapData:BitmapData, ratio:Number):BitmapData {
			ratio = Math.abs(ratio);
			var width:int = (bitmapData.width * ratio) || 1;
			var height:int = (bitmapData.height * ratio) || 1;
			var transparent:Boolean = bitmapData.transparent;
			var result:BitmapData = new BitmapData(width, height, transparent, 0x00000000);
			var matrix:Matrix = new Matrix();
			matrix.scale(ratio, ratio);
			result.draw(bitmapData, matrix, null, null, null, true);
			return result;
		}

		/**
		 * Scales a BitmapData using a nine-slices grid. DOES NOT automatically
		 * dispose the provided BitmapData object.
		 * 
		 * @param	bitmapData
		 * 			The BitmapData to resize using a nine-slice grid.
		 * 
		 * @param	width
		 * 			The width to resize the BitmapData to.
		 * 
		 * @param 	height
		 * 			The height to resize the BitmapData to.
		 * 
		 * @param	grid
		 * 			The inner cell of the nine-slice grid (row 2, column 2);
		 * 			this implicitely describes the grid in it entirety.
		 * 
		 * @return	The scaled bitmap.
		 */
		public static function gridScaleBitmap(bitmapData:BitmapData, width:Number,
			height:Number, grid:Rectangle):BitmapData {
			var resizer:ScaleBitmap = new ScaleBitmap(bitmapData);
			resizer.scale9Grid = grid;
			resizer.setSize(width, height);
			return resizer.bitmapData;
		}
	}

}
